/*
 *  linux/kernel/exit.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/tty.h>
#include <asm/segment.h>

int sys_pause(void);
int sys_close(int fd);

// 释放指定进程占用的任务槽task[n]及其任务数据结构task_struct占用的内存页面，并重新调度
void release(struct task_struct * p)
{
	int i;

	if (!p)
		return;
	for (i=1 ; i<NR_TASKS ; i++)
		if (task[i]==p) {
			// 释放任务槽
			task[i]=NULL;
			// 释放task_struct占用的内存页面
			free_page((long)p);
			schedule();
			return;
		}
	panic("trying to release non-existent task");
}

// 向指定任务p发送信号 sig
// 权限为 priv：强制发送信号的标志。即不需要考虑进程用户属性或级别而能发送信号的权
static inline int send_sig(long sig,struct task_struct * p,int priv)
{
	if (!p || sig<1 || sig>32)
		return -EINVAL;
	if (priv || (current->euid==p->euid) || suser())
		p->signal |= (1<<(sig-1));
	else
		return -EPERM;
	return 0;
}

// 遍历任务数组中所有的task，对于与当前进程同一个session组的进程设置SIGHUP信号。即关闭session组内所有进程。
static void kill_session(void)
{
	struct task_struct **p = NR_TASKS + task;
	
	// 最后一个数组元素为task[NR_TASKS-1]
	// 遍历任务数组中所有的task
	while (--p > &FIRST_TASK) {
		if (*p && (*p)->session == current->session)
			(*p)->signal |= 1<<(SIGHUP-1);
	}
}

/*
 * XXX need to check permissions needed to send signals to process
 * groups, etc. etc.  kill() permissions semantics are tricky!
 */
int sys_kill(int pid,int sig)
{
	struct task_struct **p = NR_TASKS + task;
	int err, retval = 0;

	if (!pid) while (--p > &FIRST_TASK) {
		// 如果pid == 0（0号进程），则 给其所有子进程发送信号
		if (*p && (*p)->pgrp == current->pid) 
			if ((err=send_sig(sig,*p,1)))
				retval = err;
	} else if (pid>0) while (--p > &FIRST_TASK) {
		// 如果pid>0，则 对指定进程发送信息
		if (*p && (*p)->pid == pid) 
			if ((err=send_sig(sig,*p,0)))
				retval = err;
	} else if (pid == -1) while (--p > &FIRST_TASK) {
		// 如果pid == -1，则 给所有进程发送信号（init进程除外）
		if ((err = send_sig(sig,*p,0)))
			retval = err;
	} else while (--p > &FIRST_TASK)
		// 如果pid < -1，则 给组id为|pid|的组内所有进程发送信号
		if (*p && (*p)->pgrp == -pid)
			if ((err = send_sig(sig,*p,0)))
				retval = err;
	return retval;
}

// 给指定父进程pid发送SIGCHLD信号(子进程结束信号)
// 如果没有指定的父进程，则释放自己task_struct结构内存
static void tell_father(int pid)
{
	int i;

	if (pid)
		for (i=0;i<NR_TASKS;i++) {
			if (!task[i])
				continue;
			if (task[i]->pid != pid)
				continue;
			task[i]->signal |= (1<<(SIGCHLD-1));
			return;
		}
/* if we don't find any fathers, we just release ourselves */
/* This is not really OK. Must change it to make father 1 */
	printk("BAD BAD - no father found\n\r");
	// 如果没有找到父进程，则自己释放。
	release(current);
}

// 程序退出处理函数。用于退出当前进程。
// 在系统调用处理函数 sys_exit()中被调用。
int do_exit(long code)
{
	int i;
	// 释放代码段内存段
	free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
	// 释放数据段(堆栈段)内存段
	free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
	// 处理当前进程的子进程
	for (i=0 ; i<NR_TASKS ; i++)
		// 如果是当前进程的子进程，则
		if (task[i] && task[i]->father == current->pid) {
			// 将子进程的父进程设置为1号进程
			task[i]->father = 1;
			// 如果当前进程的子进程为ZOMBIE进程，则向1号进程发送SIGCHLD信号
			if (task[i]->state == TASK_ZOMBIE)
				/* assumption task[1] is always init */
				(void) send_sig(SIGCHLD, task[1], 1);
		}
	// 关闭当前进程打开的所有文件句柄
	for (i=0 ; i<NR_OPEN ; i++)
		if (current->filp[i])
			sys_close(i);
	// 回收
	iput(current->pwd);
	current->pwd=NULL;
	iput(current->root);
	current->root=NULL;
	iput(current->executable);
	current->executable=NULL;
	if (current->leader && current->tty >= 0)
		tty_table[current->tty].pgrp = 0;
	if (last_task_used_math == current)
		last_task_used_math = NULL;
	if (current->leader)
		kill_session();
	// 把当前进程置为 TASK ZOMBIE 状态，然后去执行调度函数 schedule()，因为状态是TASK_ZOMBIE，所以不再返回。
	current->state = TASK_ZOMBIE;
	current->exit_code = code;
	tell_father(current->father);
	schedule();
	return (-1);	/* just to suppress warnings */
}

int sys_exit(int error_code)
{
	return do_exit((error_code&0xff)<<8);
	// 参数 error code 是用户程序提供的退出状态信息，只有低字节有效。
	// 把 error_code 左移8 比特是 wait(）或 waitpid( 函数的要求。低字节中将用来保存 wait()的状态信息。
	// 例如， 如果进程处于暂停状态（TASK_STOPPED)，那么其低字节就等于 0x7f。
	// 参见 sys/wait.h 文件第 13--18 行。wait（)或 waitpid(）利用这些宏就可以取得子进程的退出状态码或子进程终止的原因（信号)。
}



// 挂起当前进程，直到 pid 指定的子进程退出（终止）。这里的子进程是一个子进程集合中的其中一个子进程。
// 默认情况下（options=0）,waitpid会挂起调用程序的执行，此时父进程被阻塞，直到它的等待集合中的其中一个子进程终止。
// 等待集合的成员是由参数pid取值来确定的。根据pid的取值范围，对任务数组内的所有当前进程的子进程进行挑选，挑选出的符合条件的子进程进行等待处理：
	// 如果 pid > 0，	表示等待进程号等于 pid 的子进程（子进程集合只有1个子进程）。
	// 如果 pid=0，		表示等待进程组号等于当前进程组号的任何子进程(其中任意一个进程终止，则waitpid退出)。
	// 如果 pid < -1，	表示等待进程组号等于 pid 绝对值的任何子进程(其中任意一个进程终止，则waitpid退出)。
	// 如果 pid = -1，	表示等待任何子进程(其中任意一个进程终止，则waitpid退出)。

// 关于返回值：
	// 如果当前进程没有该指定的子进程，或者函数执行错误，waitpid()返回值为-10。
	// 如果该子进程已经终止（TASK_STOPPED或TASK_ZOMBIE），则waitpid函数返回，返回值为该pid；
	// 如果该子进程没有终止（运行态或睡眠态），但设置了WNOHANG选项，则立即返回，返回值为0；
// 关于options：
	// https://blog.csdn.net/qq_62987647/article/details/137084744
	// WNOHANG：设置WNOHANG标志来启用非阻塞等待模式。如果子进程尚未结束，waitpid将立即返回，而不是等待子进程结束。这样，父进程可以在检测到子进程仍在运行时继续执行其他任务，并在稍后再次尝试检查子进程的状态，直到子进程结束。

// 挂起当前进程，直到 pid 指定的子进程退出（终止）
// 参数 pid 是进程号；
// *stat_addr 是保存状态信息位置的指针(stat_addr代表的整数有32个比特位，其中8-15位的比特位记录着子进程的退出码；0-7位记录着退出信号，表示接收到哪个信号导致的退出行为，比如kill -9使之退出，则退出信号为9。)；参考：https://blog.csdn.net/ayf9999/article/details/135367021
// options 是 waitpid 选项。WNOHANG 拆解为 W NO HANG，即不阻塞模式。
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
	int flag, code;
	struct task_struct ** p;

	verify_area(stat_addr,4);
repeat:
	flag=0; /* 值为1表示找到一个符合要求的子进程 */
	// 遍历任务数组中的所有进程，找到符合 等待 要求的子进程，进行处理：
		// 如果有处于 TASK_STOPPED 、 TASK_ZOMBIE 状态的子进程，则return pid
		// 如果有处于运行态或睡眠态的子进程，则
	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
		// 从任务数组末端开始扫描所有任务，跳过空项、本进程项以及非当前进程的子进程项。
		if (!*p || *p == current)
			continue; /* 过滤 */
		if ((*p)->father != current->pid)
			continue;

		// 至此：待扫描的进程都是 当前进程的子进程 ，然后根据参数pid不同情况，挑选出一个符合条件的子进程

		// 如果等待的子进程号 pid>0，但与被扫描子进程p的 pid不相等，说明它是当前进程另外的子进程，于是跳过该进程，接着扫描下一个进程。
		// 即 如果 pid > 0，等待 指定pid 的子进程。
		if (pid>0) {
			if ((*p)->pid != pid)
				continue;
		} else if (!pid) {
		// 否则，如果指定等待进程的 pid=0，表示正在等待进程组号等于当前进程组号的任何子进程。
		// 如果此时被扫描进程p的进程组号与当前进程的组号不等，则跳过。
		// 即 pid = 0，即 等待 当前进程组内的任何子进程。
			if ((*p)->pgrp != current->pgrp)
				continue;
		} else if (pid != -1) {
		// 否则，如果指定的 pid < -1，表示正在等待进程组号等于 pid 绝对值的任何子进程。
		// 如果此时被扫描进程p的组号与 pid 的绝对值不等，则跳过。
		// 即 如果 pid < -1， 等待 指定进程组号内的任何子进程（组id = pid 绝对值）。
			if ((*p)->pgrp != -pid)
				continue;
		}
		//如果 pid = -1，不过滤任何子进程，即 等待 任何子进程。

		// 至此，过滤出的符合条件的子进程。
		// 进行进一步处理
		switch ((*p)->state) {
			// 子进程p处于停止状态时，此时：
				// 如果 WUNTRACED 标志没有置位，表示程序无须立刻返回， 于是继续扫描处理其他进程。
				// 如果 WUNTRACED 置位，则把状态信息 0x7f 放入stat_addr， 并立刻返回子进程号 pid。
			// 这里 0x7f 表示的返回状态使 WIFSTOPPED()宏为真。
			case TASK_STOPPED:
				if (!(options & WUNTRACED))
					continue;
				put_fs_long(0x7f,stat_addr); /* 主要是为了宏 WIFSTOPPED(s) 使用 */
				return (*p)->pid;
			// 如果子进程p处于僵死状态，则首先把它在用户态和内核态运行的时间分别累计到当前进程 （父进程）中，
			// 然后取出子进程的 pid 和退出码，并释放该子进程。最后返回子进程的退出码和 pid。
			case TASK_ZOMBIE:
				current->cutime += (*p)->utime;
				current->cstime += (*p)->stime;
				flag = (*p)->pid;
				code = (*p)->exit_code;
				// 释放子进程的任务结构体指针
				release(*p);
				put_fs_long(code,stat_addr);
				return flag;
			// 如果这个子进程口的状态既不是停止也不是僵死，那么就置 flag = 1。
			// 表示找到过一个符合要求的子进程，但是它处于运行态或睡眠态。
			default:
				flag=1;
				continue;
		}
	}
	// 在上面对任务数组扫描结束后，如果 flag 被置位，说明有符合等待要求的子进程并没有处于退出或僵死状态，即处于处于运行态或睡眠态。
	if (flag) {
		// 如果此时己设置 WNOHANG 选项（表示若没有子进程处于退出或终止态就立刻返回），就立刻返回 0，退出。
		if (options & WNOHANG)
			return 0;
		// 否则把当前进程置为可中断等待状态并重新执行调度。
		current->state=TASK_INTERRUPTIBLE;
		schedule();
		// 当又开始执行本进程时，如果本进程没有收到除 SIGCHLD 以外的信号，则还是重复处理。
			// 将SIGCHLD信号位复位
		if (!(current->signal &= ~(1<<(SIGCHLD-1))))
			goto repeat;
		else
			//否则，返回错误的中断调用
			return -EINTR;
	}
	// 若没有找到符合要求的子进程（flag=0），则返回出错码（子进程不存在）。
	return -ECHILD;
}


